
#include "common.h"
/*
  -----------依赖的外部头文件-----------
*/
#include <stdio.h>
#include <stdlib.h>
// #include <platform.h>
#include <microhttpd.h>
//#include "platform.h"
// #include "microhttpd.h"
//#include "open62541.h"
#include <open62541.h>
#include <sqlite3.h>
#include <signal.h>
#include <pthread.h>
//#include "modbus.h"
#include <modbus.h>
//用来运行FRP进程的
#include <unistd.h> /* for fork */
#include <sys/types.h> /* for pid_t */
#include <sys/wait.h> /* for wait */
//#include "SQLiteBackend.h"
/*
  -----------依赖的外部头文件-----------
*/

/*
  -----------项目内定义的头文件-----------
*/
#include "http_function.h"
//#include "opc_fun.h"
/*
opc变量操作相关的内容，主要是新增
*/
/*
  -----------项目内定义的头文件-----------
*/

/*通过宏定义来配置http和opc服务器分别占用的端口号*/
#define SERVERKEYFILE "./secret/http_server.key"
#define SERVERCERTFILE "./secret/http_server.pem"
// #define SERVERKEYFILE "./server.key"
// #define SERVERCERTFILE "./server.pem"
#define http_port_num 9999
#define opc_server_port_num 4000
#define MAX_history_data_NUM 1024
/*通过宏定义来配置http和opc服务器分别占用的端口号*/
#define PAGE \
  "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
static char* load_file (const char *filename);
static long get_file_size (const char *filename);
/*
全局变量，定义一次，其他文件中使用的时候加上extern标识符即可
*/
sqlite3 *db;
UA_Server *haussentech_opc_server = NULL;
modbus_t *ctx;
//用于保证导入opc auth的信息能从sqlite中完整加载
bool main_sql_cb_done = false;

//每个变量在创建的时候，都需要注册这个采集器，保证历史数据可以被收集
UA_HistoryDataGathering hs_gather;

extern char* temp_history_data_buffer[MAX_history_data_NUM];
extern char* temp_record_date_buffer[MAX_history_data_NUM];
/*
全局变量，定义一次，其他文件中使用的时候加上extern标识符即可
*/
/* Test Certificate */

static volatile UA_Boolean running = true;
void stopHandler(int sign){
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

//限制系统最大能够容纳的账户数量
#define MAX_ACCOUNT_NUM 32
// UA_UsernamePasswordLogin logins[MAX_ACCOUNT_NUM] = {
//     {UA_STRING_STATIC("HaoSheng"), UA_STRING_STATIC("test1")},
//     {UA_STRING_STATIC("zyl"), UA_STRING_STATIC("test2")}
// };
UA_UsernamePasswordLogin logins[MAX_ACCOUNT_NUM];
//UA_UsernamePasswordLogin logins[16];
//int actual_account_size = -1;

char iovfs_name[32];
char iovfs_protocol[32];
static int Callback_import_opc_var_from_sqlite(void *data, int argc, char **argv, char **azColName){
    int i;
    fprintf(stderr, "%s: ", (const char*)data);
    for(i=0; i<argc; i++){
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
        if(0 == strcmp(azColName[i], "name")){
            strcpy(iovfs_name,argv[i]); 
        }
        if(0 == strcmp(azColName[i], "protocol")){
            strcpy(iovfs_protocol,argv[i]); 
        }
    }
    printf("\n");
    //调用opc变量的添加函数
    if(strlen(iovfs_name)!=0 && strlen(iovfs_protocol)!=0){
      OPC_addVariable(haussentech_opc_server,
          iovfs_name,iovfs_protocol);   
    }
 
    printf("[ONE Callback Finish]\n\n");
    return 0;
}

char* temp_username[MAX_ACCOUNT_NUM];
char* temp_password[MAX_ACCOUNT_NUM];
//sqlite的回调函数里面，调用一次，只会出一行的数据
//如果出现多行的情况,回调函数会被多次调用
int colum_num = 0;
static int Callback_opc_auth_username_password(void *data, int argc, char **argv, char **azColName){
    int i;
    fprintf(stderr, "%s: ", (const char*)data);
    for(i=0; i<argc; i++){
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
        //不知道为什么，像UA_String这类复杂的数据结构不能用在sqlite的回调函数里面
        if(0 == strcmp(azColName[i], "username")){
            //二级数组在赋值的时候要考虑分配内存空间的问题
            //logins[colum_num].username = UA_STRING(argv[i]);
            strcpy(temp_username[colum_num],argv[i]); 
        }
        if(0 == strcmp(azColName[i], "password")){
            //logins[colum_num].password = UA_STRING(argv[i]);
            strcpy(temp_password[colum_num],argv[i]); 
        }
    }
    printf("\n");
    //对行号做自增的处理，用于信息录入时的一种下标机制
    colum_num++;
    printf("function finish\n");
    return 0;
}

void sql_select_get_opc_auth(){         
    //query databse get config first
    //设置colum_num为0，并且结束标志为false
    main_sql_cb_done = false;
    colum_num = 0;

    const char* data = "Callback function called";

    char sql[512] = "select * from opc_auth ;";
    /*
        modify here
    */
    printf("[SQL]%s\n",sql);
    char *zErrMsg = 0;
    int rc;
    // modify callback
    rc = sqlite3_exec(db, sql, Callback_opc_auth_username_password, (void*)data, &zErrMsg);
    if( rc != SQLITE_OK ){
        fprintf(stderr, "SQL error: %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
    }else{
        fprintf(stdout, "SQL select done successfully\n");
        main_sql_cb_done = true;
        //把回调函数的结束标志放在这个语句后面，因为callback会被多次执行，并不能事先决定何时结束
    }
}

void* opc_routine(){
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);
    for(int i=0;i<MAX_ACCOUNT_NUM;i++){
      char *str_username = (char *) malloc(sizeof(char) * 16);
      temp_username[i] = str_username;
      char *str_password =  (char *) malloc(sizeof(char) * 16);
      temp_password[i] = str_password;
    }
    for(int i=0;i<MAX_history_data_NUM;i++){
      //单条data记录不超过1024个字节
      temp_history_data_buffer[i] = (char *) malloc(sizeof(char) * 1024);
      temp_record_date_buffer[i] = (char *) malloc(sizeof(char) * 128);
    }
    sql_select_get_opc_auth();
    while (!main_sql_cb_done)
    {
        //sql_cb_done为全局变量，循环等待回调函数执行结束，将其设为真
        //继续执行，目的就是把涉及到回调函数的场景，能够变成顺序执行
        //而不是陷入层层嵌套的回调地狱'
    }
    for(int i=0;i<colum_num;i++){
      logins[i].username = UA_STRING(temp_username[i]);
      logins[i].password = UA_STRING(temp_password[i]);   
    }
    //完成用户账户和密码信息的导入工作
    /* 加载server的证书和私匙 */
    UA_ByteString certificate = UA_BYTESTRING_NULL;
    UA_ByteString privateKey  = UA_BYTESTRING_NULL;
    // certificate = loadFile("./opc_server_cert.txt");
    // privateKey = loadFile("./opc_server_key.txt");
    certificate = loadFile("./secret/opc_server_cert.txt");
    privateKey = loadFile("./secret/opc_server_key.txt");
    /* 加载trustlist */
    size_t trustListSize = 0;
    //UA_STACKARRAY(UA_ByteString, trustList, trustListSize);
    //trustList[0] = loadFile("./cert_info/client_cert.der");
    UA_ByteString *trustList = NULL;

    /* Loading of a issuer list, not used in this application */
    size_t issuerListSize = 0;
    UA_ByteString *issuerList = NULL;

    /* Loading of a revocation list currently unsupported */
    UA_ByteString *revocationList = NULL;
    size_t revocationListSize = 0;

    haussentech_opc_server = UA_Server_new();
    UA_ServerConfig *config = UA_Server_getConfig(haussentech_opc_server);
    //在配置访问配置之前，需要对系统进行最基础的配置，否则会报错
    UA_ServerConfig_setMinimal(config,opc_server_port_num,NULL);                                          
    config->accessControl.clear(&config->accessControl); 
    UA_StatusCode retval = UA_AccessControl_default(config, false,NULL,
             &config->securityPolicies[config->securityPoliciesSize-1].policyUri, MAX_ACCOUNT_NUM, logins);  
    //UA_ServerConfig_setDefaultWithSecurityPolicies会导致初始化无关的一些信息，比如配置加密策略以后就会把访问控制给抹掉
    //需要通过add的方法才可以，对于服务端SecurityPolicies和Endpoint都要进行配置
    UA_ServerConfig_addAllSecurityPolicies(config,&certificate,&privateKey);    
    UA_ServerConfig_addEndpoint(config, UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"),
                            UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);  
    UA_ByteString_clear(&certificate);
    UA_ByteString_clear(&privateKey);
    for(size_t i = 0; i < trustListSize; i++)
        UA_ByteString_clear(&trustList[i]);
    
    //历史数据采集配置，支持检测64个变量的变化，默认的值是1
    hs_gather = UA_HistoryDataGathering_Default(64);
    config->historyDatabase = UA_HistoryDatabase_default(hs_gather);
    /*
    2023-2-14
    关键改动，OPC UA变量的导入，可以在UA_Server_run函数之前
    按照之前的逻辑，高频的会报段溢出的错误的，说明sqlite可能存在多线程操作
    或者这里面的顺序很复杂，但是保持这个顺序就可以了
    */
    sql_select_import_opc_var_from_sqlite();
    UA_Server_run(haussentech_opc_server, &running);
    /*
    旧的逻辑：会不定时的导致段错误，opc_server在这种流程下没有恰当的初始化
    不知道威慑呢蜜
    
    在OPC server启动以后，再执行变量的添加程序
    先通过sql进行查询，把所有的变量都找出来，然后选择特定的列，并塞到回调函数里面
    */
    UA_Server_delete(haussentech_opc_server);
}

void sql_select_import_opc_var_from_sqlite(){         
    //query databse get config first
    //设置colum_num为0，并且结束标志为false
    main_sql_cb_done = false;
    colum_num = 0;

    const char* data = "Import_Opc_Var_From_Sqlite\n";

    char sql[512] = "select * from opc_var_config ;";
    /*
        modify here
    */
    printf("[SQL]%s\n",sql);
    char *zErrMsg = 0;
    int rc;
    // modify callback
    rc = sqlite3_exec(db, sql, Callback_import_opc_var_from_sqlite, (void*)data, &zErrMsg);
    if( rc != SQLITE_OK ){
        fprintf(stderr, "SQL error: %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
    }else{
        fprintf(stdout, "SQL select done successfully\n");
        main_sql_cb_done = true;
        //把回调函数的结束标志放在这个语句后面，因为callback会被多次执行，并不能事先决定何时结束
    }
}

void* modbus_routine(){
    printf("Compiled with libmodbus version %s (%06X)\n",
           LIBMODBUS_VERSION_STRING,
           LIBMODBUS_VERSION_HEX);
    printf("Linked with libmodbus version %d.%d.%d\n",
           libmodbus_version_major,
           libmodbus_version_minor,
           libmodbus_version_micro);

    if (LIBMODBUS_VERSION_CHECK(2, 1, 0)) {
        printf("The functions to read/write float values are available (2.1.0).\n");
    }

    if (LIBMODBUS_VERSION_CHECK(2, 1, 1)) {
        printf("Oh gosh, brand new API (2.1.1)!\n");
    }
}
static void request_completed (void *cls, struct MHD_Connection *connection,
void **con_cls, enum MHD_RequestTerminationCode toe)
{
  struct connection_info_struct *con_info = *con_cls;
  (void) cls; /* Unused. Silent compiler warning. */
  (void) connection; /* Unused. Silent compiler warning. */
  (void) toe; /* Unused. Silent compiler warning. */
  if (NULL == con_info)
  return;
  if (con_info->connectiontype == POST)
  {
  MHD_destroy_post_processor (con_info->postprocessor);
  if (con_info->answerstring)
  free (con_info->answerstring);
  }
  free (con_info);
  *con_cls = NULL;
}
int main (int argc,
      char *const *argv)
{
  if(argc > 1 && atoi(argv[1])==1){
    UA_ByteString certificate = UA_BYTESTRING_NULL;
    UA_ByteString privateKey  = UA_BYTESTRING_NULL;
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                "Trying to create a certificate.");
    UA_String subject[3] = {UA_STRING_STATIC("C=DE"),
                        UA_STRING_STATIC("O=SampleOrganization"),
                        UA_STRING_STATIC("CN=Open62541Server@localhost")};
    UA_UInt32 lenSubject = 3;
    UA_String subjectAltName[2]= {
        UA_STRING_STATIC("DNS:localhost"),
        UA_STRING_STATIC("URI:urn:open62541.server.application")
    };
    UA_UInt32 lenSubjectAltName = 2;
    UA_StatusCode statusCertGen =
    UA_CreateCertificate(UA_Log_Stdout,
                          subject, lenSubject,
                          subjectAltName, lenSubjectAltName,
                          0, UA_CERTIFICATEFORMAT_DER,
                          &privateKey, &certificate);    
    if(statusCertGen != UA_STATUSCODE_GOOD) {
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
            "Generating Certificate failed: %s",
            UA_StatusCode_name(statusCertGen));
        return EXIT_FAILURE;
    } 
    writeFile("./secret/opc_server_key.txt", privateKey); 
    writeFile("./secret/opc_server_cert.txt", certificate);
    system("./secret/while_list.sh");
    //system("./secret/auto-generate-cert.sh");
  }

 
   char *zErrMsg = 0;
   int rc;

   rc = sqlite3_open("opc_server.db", &db);

   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      exit(0);
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }
   //sqlite3_close(db);
   //close will release variable


  //initialize the bottom layer driver
  //then we will extract config from local file
  
    // ctx = modbus_new_tcp("127.0.0.1", 1502);
    // modbus_set_debug(ctx, TRUE);
    // if (modbus_connect(ctx) == -1) {
    //     fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
    //     modbus_free(ctx);
    //     return -1;
    // }
    // uint16_t tab_rq_registers[1]={0x1234};
    // int rt = modbus_write_registers(ctx, 1, 1, tab_rq_registers);
    // if(rt==1){
    //   printf("[modbus]:succesful write\n");
    // }else{
    //   printf("[fail]:write\n");
    // }
  char *key_pem;
  char *cert_pem;

  key_pem = load_file (SERVERKEYFILE);
  cert_pem = load_file (SERVERCERTFILE);

  if ((key_pem == NULL) || (cert_pem == NULL))
  {
    printf ("The key/certificate files could not be read.\n");
    return 1;
  }
  pthread_t opc_t;
  pthread_create(&opc_t,NULL,&opc_routine,argv);
  pthread_t modbus_t;
  pthread_create(&opc_t,NULL,&modbus_routine,NULL);
  struct MHD_Daemon *d;

  // d = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
  //   http_port_num, NULL, NULL,
  //   &RESTful_API_Entry, NULL,
  //   MHD_OPTION_NOTIFY_COMPLETED, request_completed,
  //   NULL, MHD_OPTION_END);

  /*
  MHD_OPTION_NOTIFY_COMPLETED, request_completed,NULL,
  后面的NULL是搭配MHD_OPTION_NOTIFY_COMPLETED 选项的，这个选项要求背后跟两个参数，
  第二个参数可以是null
  */
  // while(haussentech_opc_server == NULL){
  //   //等待opc_server启动成功，开始为其导入变量
  // }   
  
  d = MHD_start_daemon (/* MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, */
    MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG| MHD_USE_TLS,
    /* MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG | MHD_USE_POLL, */
    /* MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG | MHD_USE_POLL, */
    /* MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, */
    http_port_num,
    NULL, NULL, &RESTful_API_Entry, NULL,
    MHD_OPTION_NOTIFY_COMPLETED, request_completed,NULL,
    MHD_OPTION_HTTPS_MEM_KEY, key_pem,
    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
    MHD_OPTION_END);

  
  if (NULL == d){
      printf ("%s\n", cert_pem);

      free (key_pem);
      free (cert_pem);

      return 1;
  }
  if (d == NULL){
    printf("demon fail start!!\n");
    return 1;
  }
    
  getc(stdin);
  MHD_stop_daemon (d);
  return 0;
}

static char* load_file (const char *filename)
{
  FILE *fp;
  char *buffer;
  long size;

  size = get_file_size (filename);
  if (size == 0)
    return NULL;

  fp = fopen (filename, "rb");
  if (!fp)
    return NULL;

  buffer = malloc (size);
  if (!buffer)
    {
      fclose (fp);
      return NULL;
    }

  if (size != fread (buffer, 1, size, fp))
    {
      free (buffer);
      buffer = NULL;
    }

  fclose (fp);
  return buffer;
}
static long get_file_size (const char *filename)
{
  FILE *fp;

  fp = fopen (filename, "rb");
  if (fp)
    {
      long size;

      if ((0 != fseek (fp, 0, SEEK_END)) || (-1 == (size = ftell (fp))))
        size = 0;

      fclose (fp);

      return size;
    }
  else
    return 0;
}

